/*
 * OpenParts
 * A dynamic-loading components framework for GWT
 * Copyright (C) 2011 Christophe Bouthier  [chris{AT}binary-gastronome{DOT}fr]
 *
 * This work is partially based on work I have done at INRIA (http://www.inria.fr) 
 * in the context of the Qualipso European Project (http://qualipso.org/),
 * The work done at INRIA is Copyright (C) 2006-2011 INRIA
 *
 * This work is distributed under the LGPL version 3
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3
 * as published by the Free Software Foundation. See the GNU
 * Lesser General Public License in LGPL.txt for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package fr.openparts.OpenParts.test.gwt.tests;

import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.Timer;

import fr.openparts.OpenParts.client.OPBinding;
import fr.openparts.OpenParts.client.OPCommandCallback;
import fr.openparts.OpenParts.client.OPCommandFailureReason;
import fr.openparts.OpenParts.client.OPParams;
import fr.openparts.OpenParts.client.OPPartsLoader;
import fr.openparts.OpenParts.client.OPScheduler;
import fr.openparts.OpenParts.client.OPShell;
import fr.openparts.OpenParts.test.gwt.dummyPart.client.DummyPart;

/**
 * GWT tests for the event bus of OPShell.
 * 
 * @author Christophe Bouthier [chris{AT}binary-gastronome{DOT}fr]
 * @creation.date 16 December 2009
 */
public class OPShellDispatchEventTestGwt extends GWTTestCase {

    private static final String SERVICE_NAME = "dummyService";

    private static final int TIMER_TIMEOUT = 5500;
    private static final int TEST_TIMEOUT = 6000;

    private OPCommandCallback callback;
    private Timer timer;

    private int onSuccessCalled = 0;
    private int onFailCalled = 0;
    private OPCommandFailureReason onFailReason;

    /**
     * Return the name of the tested GWT Module.
     * 
     * @see com.google.gwt.junit.client.GWTTestCase#getModuleName()
     */
    @Override
    public final String getModuleName() {
        return "fr.openparts.OpenParts.test.gwt.TestOpenParts";
    }

    /**
     * Called at the start of each test to set up the part.
     * 
     * @see com.google.gwt.junit.client.GWTTestCase#gwtSetUp()
     */
    @Override
    protected void gwtSetUp() throws Exception {
        super.gwtSetUp();
        OPPartsLoader.bind();
        OPScheduler.bind();
        callback = new OPCommandCallback() {
            @Override
            public void onSuccess() {
                ++onSuccessCalled;
            }

            @Override
            public void onFail(final OPCommandFailureReason reason) {
                ++onFailCalled;
                onFailReason = reason;
            }
        };
    }

    /**
     * Called at the end of each test to clean up the mess.
     * 
     * @see com.google.gwt.junit.client.GWTTestCase#gwtTearDown()
     */
    @Override
    protected final void gwtTearDown() throws Exception {
        super.gwtTearDown();
        onSuccessCalled = 0;
        onFailCalled = 0;
        onFailReason = null;
        emptyListeners(OPShell.opBindingSharedInstance());
        unbindOPPartsLoader(OPShell.opBindingSharedInstance());
        unbindOPScheduler(OPShell.opBindingSharedInstance());
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
    }

    /**
     * Test adding a new listener. There should be only one listener registered.
     */
    public final void testRegisteringListener() {
        final OPBinding binding = OPShell.opBindingSharedInstance();
        assertEquals("There should be no listeners", 0, binding.getNumberOfEventBusListeners());

        final DummyPart dummyPart = new DummyPart();
        dummyPart.bind();

        assertEquals("There should be one and only one listener", 1, binding.getNumberOfEventBusListeners());
    }

    /**
     * Test dispatching event to listeners.
     */
    public final void testDispatchingEvent() {
        final DummyPart dummyPart = new DummyPart();
        dummyPart.bind();

        assertEquals("Listener should have been called 0 times.", 0, dummyPart.eventHandlerCalls);
        assertEquals("Specific listener should have been called 0 times.", 0, dummyPart.specificHandlerCalls);

        OPShell.dispatchEvent("test");

        assertEquals("Listener should have been called 1 times.", 1, dummyPart.eventHandlerCalls);
        assertEquals("Specific listener should still have been called 0 times.", 0, dummyPart.specificHandlerCalls);
    }

    /**
     * Test dispatching specific event to listeners.
     */
    public final void testDispatchingSpecificEvent() {
        final DummyPart dummyPart = new DummyPart();
        dummyPart.bind();

        assertEquals("Listener should have been called 0 times.", 0, dummyPart.eventHandlerCalls);
        assertEquals("Specific listener should have been called 0 times.", 0, dummyPart.specificHandlerCalls);

        OPShell.dispatchEvent("SPECIFIC_EVENT");

        assertEquals("Listener should still have been called 0 times.", 0, dummyPart.eventHandlerCalls);
        assertEquals("Specific listener should have been called 1 times.", 1, dummyPart.specificHandlerCalls);
    }

    /**
     * Test dispatching event with params. Should NOT get something null.
     */
    public final void testDispatchingEventWithParams() {
        final OPParams params = new OPParams("name", "value");

        final DummyPart dummyPart = new DummyPart();
        dummyPart.bind();

        assertNull("params should be null.", dummyPart.params);

        OPShell.dispatchEvent("test", params);

        assertNotNull("params should NOT be null.", dummyPart.params);
    }

    /**
     * Test dispatching event to listeners with callback. Should call the callback onSuccess.
     */
    public final void testDispatchingEventWithCallback() {
        final DummyPart dummyPart = new DummyPart();
        dummyPart.bind();

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        OPShell.dispatchEvent("test", callback);

        assertEquals("onSuccess should have been called now", 1, onSuccessCalled);
        assertEquals("onFail should still not have been called", 0, onFailCalled);
    }

    /**
     * Test dispatching event with callback and params. Should call the callback onSuccess.
     */
    public final void testDispatchingEventWithCallbackAndParams() {
        final OPParams params = new OPParams("name", "value");

        final DummyPart dummyPart = new DummyPart();
        dummyPart.bind();

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);
        assertNull("params should be null.", dummyPart.params);

        OPShell.dispatchEvent("test", params, callback);

        assertEquals("onSuccess should have been called now", 1, onSuccessCalled);
        assertEquals("onFail should still not have been called", 0, onFailCalled);
        assertNotNull("params should NOT be null.", dummyPart.params);
    }

    /**
     * Test dispatching a null event. Should raise an assertion error.
     */
    public final void testDispatchingNullEvent() {
        if (assertionsAreEnabled()) {
            try {
                OPShell.dispatchEvent(null);
                fail("OPShell should raise an assertion error when dispatching a null event.");
            } catch (AssertionError e) {
                assertTrue("Error should be an AssertionError", e instanceof AssertionError);
            }
        }
    }

    /**
     * Test dispatching event to a specific listener.
     */
    public final void testDispatchingEventTo() {
        final DummyPart dummyPart = new DummyPart();
        dummyPart.bind();

        assertEquals("Listener should have been called 0 times.", 0, dummyPart.eventHandlerCalls);
        assertEquals("Specific listener should have been called 0 times.", 0, dummyPart.specificHandlerCalls);

        OPShell.dispatchEventTo(SERVICE_NAME, "test");
        OPShell.notifyLoadingCompleted(SERVICE_NAME);

        assertEquals("Listener should have been called 1 times.", 1, dummyPart.eventHandlerCalls);
        assertEquals("Specific listener should still have been called 0 times.", 0, dummyPart.specificHandlerCalls);
    }

    /**
     * Test dispatching specific event to specific listeners.
     */
    public final void testDispatchingSpecificEventTo() {
        final DummyPart dummyPart = new DummyPart();
        dummyPart.bind();

        assertEquals("Listener should have been called 0 times.", 0, dummyPart.eventHandlerCalls);
        assertEquals("Specific listener should have been called 0 times.", 0, dummyPart.specificHandlerCalls);

        OPShell.dispatchEventTo(SERVICE_NAME, "SPECIFIC_EVENT");
        OPShell.notifyLoadingCompleted(SERVICE_NAME);

        assertEquals("Listener should still have been called 0 times.", 0, dummyPart.eventHandlerCalls);
        assertEquals("Specific listener should have been called 1 times.", 1, dummyPart.specificHandlerCalls);
    }

    /**
     * Test dispatching event to specific part with params. Should NOT get something null.
     */
    public final void testDispatchingEventToWithParams() {
        final OPParams params = new OPParams("name", "value");

        final DummyPart dummyPart = new DummyPart();
        dummyPart.bind();

        assertNull("params should be null.", dummyPart.params);

        OPShell.dispatchEventTo(SERVICE_NAME, "test", params);
        OPShell.notifyLoadingCompleted(SERVICE_NAME);

        assertNotNull("params should NOT be null.", dummyPart.params);
    }

    //

    /**
     * Test dispatching event to a specific listener with callback. Should call the callback onSuccess.
     */
    public final void testDispatchingEventToWithCallback() {
        final DummyPart dummyPart = new DummyPart();
        dummyPart.bind();

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        OPShell.dispatchEventTo(SERVICE_NAME, "test", callback);
        OPShell.notifyLoadingCompleted(SERVICE_NAME);

        assertEquals("onSuccess should have been called now", 1, onSuccessCalled);
        assertEquals("onFail should still not have been called", 0, onFailCalled);
    }

    /**
     * Test dispatching event to specific part with callback and params. Should call the callback onSuccess.
     */
    public final void testDispatchingEventToWithCallbackAndParams() {
        final OPParams params = new OPParams("name", "value");

        final DummyPart dummyPart = new DummyPart();
        dummyPart.bind();

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);
        assertNull("params should be null.", dummyPart.params);

        OPShell.dispatchEventTo(SERVICE_NAME, "test", params, callback);
        OPShell.notifyLoadingCompleted(SERVICE_NAME);

        assertEquals("onSuccess should have been called now", 1, onSuccessCalled);
        assertEquals("onFail should still not have been called", 0, onFailCalled);
        assertNotNull("params should NOT be null.", dummyPart.params);
    }

    /**
     * Test dispatching event to specific part with failure when loading the part. Should call back the callback onFail with a pre command failed reason.
     */
    public final void testDispatchingEventToWithLoadFailure() {
        final DummyPart dummyPart = new DummyPart();
        dummyPart.bind();

        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        OPShell.dispatchEventTo(SERVICE_NAME, "test", callback);

        timer = new Timer() {
            public void run() {
                assertEquals("onSuccess should still not have been called", 0, onSuccessCalled);
                assertEquals("onFail should have been called now", 1, onFailCalled);
                assertNotNull("reason shouldn't be null", onFailReason);
                assertEquals("reason should be pre command failed", OPCommandFailureReason.TYPE_PRE_COMMAND_FAILED, onFailReason.getType());
                assertNotNull("reason params shouldn't be null", onFailReason.getParams());
                assertEquals("reason service should be " + SERVICE_NAME, SERVICE_NAME,
                        onFailReason.getParams().getParamValue(OPCommandFailureReason.PARAM_SERVICE));
                finishTest();
            }
        };

        timer.schedule(TIMER_TIMEOUT);
        delayTestFinish(TEST_TIMEOUT);
    }

    /**
     * Test dispatching event to specific part with invalid service. Should call back the callback onFail with a no such service reason.
     */
    public final void testDispatchingEventToWithInvalidService() {
        assertEquals("onSuccess should not have been called yet", 0, onSuccessCalled);
        assertEquals("onFail should not have been called yet", 0, onFailCalled);

        OPShell.dispatchEventTo(SERVICE_NAME + "_dummy", "test", null, callback);
        OPShell.notifyLoadingCompleted(SERVICE_NAME + "_dummy");

        assertEquals("onSuccess should still not have been called", 0, onSuccessCalled);
        assertEquals("onFail should have been called now", 1, onFailCalled);
        assertNotNull("reason shouldn't be null", onFailReason);
        assertEquals("reason should be no such service", OPCommandFailureReason.TYPE_NO_SUCH_SERVICE, onFailReason.getType());
        assertNotNull("reason params shouldn't be null", onFailReason.getParams());
        assertEquals("reason service should be " + SERVICE_NAME + "_dummy", SERVICE_NAME + "_dummy",
                onFailReason.getParams().getParamValue(OPCommandFailureReason.PARAM_SERVICE));
    }

    /**
     * Test dispatching a null event to specific part. Should raise an assertion error.
     */
    public final void testDispatchingNullEventTo() {
        if (assertionsAreEnabled()) {
            final DummyPart dummyPart = new DummyPart();
            dummyPart.bind();

            try {
                OPShell.dispatchEventTo(SERVICE_NAME, null);
                fail("OPShell should raise an assertion error when dispatching a null event.");
            } catch (AssertionError e) {
                assertTrue("Error should be an AssertionError", e instanceof AssertionError);
            }
        }
    }

    /**
     * Test dispatching a event to specific null part. Should raise an assertion error.
     */
    public final void testDispatchingEventToNullService() {
        if (assertionsAreEnabled()) {
            try {
                OPShell.dispatchEventTo(null, "test");
                fail("OPShell should raise an assertion error when dispatching a event to a specific null service.");
            } catch (AssertionError e) {
                assertTrue("Error should be an AssertionError", e instanceof AssertionError);
            }
        }
    }

    /**
     * Unbind the OPScheduler.
     */
    private final native void unbindOPScheduler(final OPBinding opbinding) /*-{
        opbinding.scheduler = null;
    }-*/;

    /**
     * Unbind the OPPartsLoader.
     */
    private final native void unbindOPPartsLoader(final OPBinding binding) /*-{
        binding.partsLoader = null;
    }-*/;

    /**
     * Native helper method to clean up event bus listener.
     */
    private final native void emptyListeners(final OPBinding binding) /*-{
        binding.listeners = {};
    }-*/;

    /**
     * Check if assertions are enabled.
     * 
     * @return true if assertions are enabled, false otherwise
     */
    private final boolean assertionsAreEnabled() {
        try {
            assert false;
            return false;
        } catch (AssertionError e) {
            return true;
        }
    }
}
